package com.googlecode.npgame.assets;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import android.content.res.AssetManager;
import android.opengl.GLES20;

import com.googlecode.npgame.graphics.shaders.Program;
import com.googlecode.npgame.graphics.shaders.Shader;
import com.googlecode.npgame.levels.Level;

/**
 * @author Josh (joshbruning)
 */
public class Assets {

  private static final String BACKGROUND_VERTEX_LOCATION = "graphics/shaders/background.vert";
  private static final String BACKGROUND_FRAGMENT_LOCATION = "graphics/shaders/background.frag";
  private static final String CLOUDS_VERTEX_LOCATION = "graphics/shaders/clouds.vert";
  private static final String CLOUDS_FRAGMENT_LOCATION = "graphics/shaders/clouds.frag";
  private static final String ELEMENT_VERTEX_LOCATION = "graphics/shaders/element.vert";
  private static final String ELEMENT_FRAGMENT_LOCATION = "graphics/shaders/element.frag";
  private static final String SIMPLE_VERTEX_LOCATION = "graphics/shaders/simple.vert";
  private static final String SIMPLE_FRAGMENT_LOCATION = "graphics/shaders/simple.frag";
  private static final String[] LEVEL_LOCATIONS = new String[] {
      "levels/level1.xml", "levels/level2.xml", "levels/level3.xml",
      "levels/level4.xml", "levels/level5.xml", "levels/level6.xml",
      "levels/level7.xml", "levels/level8.xml", "levels/level9.xml",
      "levels/bonus.xml" };

  private static boolean initialized = false;

  private static AssetManager manager;

  private static Program backgroundProgram;
  private static Shader backgroundVertex;
  private static Shader backgroundFragment;

  private static Program cloudsProgram;
  private static Shader cloudsVertex;
  private static Shader cloudsFragment;

  private static Program elementProgram;
  private static Shader elementVertex;
  private static Shader elementFragment;

  private static Program simpleProgram;
  private static Shader simpleVertex;
  private static Shader simpleFragment;

  private static List<Level> levels;

  public static Program getBackgroundProgram() {
    if (backgroundProgram == null) {
      backgroundProgram = new Program(getBackgroundVertex(),
          getBackgroundFragment());
    }

    return backgroundProgram;
  }

  public static Shader getBackgroundVertex() {
    if (backgroundVertex == null) {
      backgroundVertex = new Shader(GLES20.GL_VERTEX_SHADER, readAsset(manager,
          BACKGROUND_VERTEX_LOCATION));
    }

    return backgroundVertex;
  }

  public static Shader getBackgroundFragment() {
    if (backgroundFragment == null) {
      backgroundFragment = new Shader(GLES20.GL_FRAGMENT_SHADER, readAsset(
          manager, BACKGROUND_FRAGMENT_LOCATION));
    }

    return backgroundFragment;
  }

  public static Program getCloudsProgram() {
    if (cloudsProgram == null) {
      cloudsProgram = new Program(getCloudsVertex(), getCloudsFragment());
    }

    return cloudsProgram;
  }

  public static Shader getCloudsVertex() {
    if (cloudsVertex == null) {
      cloudsVertex = new Shader(GLES20.GL_VERTEX_SHADER, readAsset(manager,
          CLOUDS_VERTEX_LOCATION));
    }

    return cloudsVertex;
  }

  public static Shader getCloudsFragment() {
    if (cloudsFragment == null) {
      cloudsFragment = new Shader(GLES20.GL_FRAGMENT_SHADER, readAsset(manager,
          CLOUDS_FRAGMENT_LOCATION));
    }

    return cloudsFragment;
  }

  public static Program getElementProgram() {
    if (elementProgram == null) {
      elementProgram = new Program(getElementVertex(), getElementFragment());
    }

    return elementProgram;
  }

  public static Shader getElementVertex() {
    if (elementVertex == null) {
      elementVertex = new Shader(GLES20.GL_VERTEX_SHADER, readAsset(manager,
          ELEMENT_VERTEX_LOCATION));
    }

    return elementVertex;
  }

  public static Shader getElementFragment() {
    if (elementFragment == null) {
      elementFragment = new Shader(GLES20.GL_FRAGMENT_SHADER, readAsset(
          manager, ELEMENT_FRAGMENT_LOCATION));
    }

    return elementFragment;
  }

  public static Program getSimpleProgram() {
    if (simpleProgram == null) {
      simpleProgram = new Program(getSimpleVertex(), getSimpleFragment());
    }

    return simpleProgram;
  }

  public static Shader getSimpleVertex() {
    if (simpleVertex == null) {
      simpleVertex = new Shader(GLES20.GL_VERTEX_SHADER, readAsset(manager,
          SIMPLE_VERTEX_LOCATION));
    }

    return simpleVertex;
  }

  public static Shader getSimpleFragment() {
    if (simpleFragment == null) {
      simpleFragment = new Shader(GLES20.GL_FRAGMENT_SHADER, readAsset(manager,
          SIMPLE_FRAGMENT_LOCATION));
    }

    return simpleFragment;
  }

  public static List<Level> getLevels() {
    if (levels == null) {
      levels = new ArrayList<Level>();
      for (String location : LEVEL_LOCATIONS) {
        Document document = readXmlAsset(manager, location);
        levels.add(new Level(document));
      }
    }

    return levels;
  }

  public static synchronized void preloadAssets(AssetManager manager) {
    if (initialized) {
      return;
    }

    Assets.manager = manager;
    // Cannot load the shaders here as they require the OpenGL context.
    getLevels();
    initialized = true;
  }

  private static InputStream getAssetInputStream(AssetManager assetManager,
      String fileName) {
    try {
      return assetManager.open(fileName);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  private static Document readXmlAsset(AssetManager assetManager, String fileName) {
    try {
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = factory.newDocumentBuilder();
      return builder.parse(getAssetInputStream(manager, fileName));
    } catch (ParserConfigurationException e) {
      throw new RuntimeException(e);
    } catch (SAXException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  
  private static String readAsset(AssetManager assetManager, String fileName) {
    InputStream is = getAssetInputStream(assetManager, fileName);
    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    String line = null;
    StringBuilder builder = new StringBuilder();
    String separater = System.getProperty("line.separator");
    try {
      while ((line = reader.readLine()) != null) {
        builder.append(line);
        builder.append(separater);
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    return builder.toString();
  }
}
